//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#ifndef __ELASTOS_ATARW_H__
#define __ELASTOS_ATARW_H__

#define MAX_IDE_RETRY_NUM  0x0fff

INLINE static void Delay(int n)
{
    uint8_t u8delay = 0xFF;

    if (!n)
        n = 1;
    while (n--) {
        while (u8delay--);
        u8delay = 0xFF;
    }
}

INLINE void ClearInterrupt(ioport_t iobase)
{
    volatile UInt8 u8Status =  Inb(IDEPORT_STATUS(iobase));
}

INLINE void ResetController(ioport_t iobase)
{
    // step 1: Set IDE Ctrlr in normal state
    /* enable IDEctrlr */
    *((volatile unsigned short *)0xb4015800) &= ~0x200;

    // step 2: reset IDEDrive
    OutbDelay(IDEPORT_DEVICE_CONTROL(iobase), DCR_(DCR_SRST | DCR_nIEN));
    Outb(IDEPORT_DEVICE_CONTROL(iobase), DCR_(DCR_nIEN));
}

INLINE void MaskControllerInterrupt(ioport_t iobase, bool_t bEnable)
{
    if (bEnable)
        Outb(IDEPORT_DEVICE_CONTROL(iobase), INTENABLE);
    else
        Outb(IDEPORT_DEVICE_CONTROL(iobase), INTDISENABLE);
}

INLINE ECode WaitForBSY(ioport_t iobase)
{
    uint8_t u8Status;

    while (TRUE) {
        u8Status = Inb(IDEPORT_ALTERNATE_STATUS(iobase));
        if (!(u8Status & SR_BSY)) return NOERROR;
    }
}

//with interrupt clear
INLINE ECode WaitForStatus(ioport_t iobase, uint8_t u8Status)
{
    uint8_t Status;
    uint16_t u16Waittime = 0;

    while (TRUE) {
        Status = Inb(IDEPORT_STATUS(iobase));
        if (!(Status & SR_BSY) && (Status & u8Status))
            return NOERROR;
        DzDelay(1);
        u16Waittime++;
        if (u16Waittime >= MAX_IDE_RETRY_NUM) {
            //kprintf("error:Disk time out !0x%08x\n", iobase);
            return E_TIMED_OUT;
        }
    }
}

INLINE void DealWithError(ioport_t iobase)
{
    uint8_t u8Status = Inb(IDEPORT_STATUS(iobase)) ;
    if (SR_ERR & u8Status)
        kprintf("StatusReg:ErrorReg=(0x%x:0x%x)\n",
                u8Status, Inb(IDEPORT_ERROR(iobase)));
    if (SR_DF & u8Status)
        kprintf("Drive Fault\n");
    if (SR_CORR & u8Status)
        kprintf("corrected data error\n");
}

INLINE uint8_t ExecuteDriveDiagnostics(ioport_t iobase)
{
    ECode ec = WaitForBSY(iobase );
    if (FAILED(ec)) return 0xff;

    Outb(IDEPORT_COMMAND(iobase), IDECommand_ExecuteDriveDiagnostics);

    ec = WaitForBSY(iobase );
    if (FAILED(ec)) return 0xff;

    return Inb(IDEPORT_ERROR(iobase));
}

INLINE ECode IdentifyDevice(
    ioport_t iobase, uint_t uDriveNo, IDEDeviceInfo *pDeviceInfo)
{
    assert(0 == uDriveNo || 1 == uDriveNo);

    MaskControllerInterrupt(iobase, FALSE);

    ECode ec = WaitForBSY(iobase );
    if (FAILED(ec)) {
        kprintf("ERROR:Identify device %d failed\n", uDriveNo);
        ec = E_FAIL;
        goto EXIT;
    }

    if (0 == uDriveNo)
        Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(DHR_DEV0));
    else
        Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(DHR_DEV1));

    ec = WaitForStatus(iobase, SR_DRDY);
    if (FAILED(ec)) {
        ec = E_FAIL;
        goto EXIT;
    }

    Outb(IDEPORT_COMMAND(iobase), IDECommand_IdentifyDevice);

    ec = WaitForBSY(iobase );
    if (FAILED(ec)) {
        kprintf("ERROR:Identify device %d failed\n", uDriveNo);
        DealWithError(iobase);
        ec = E_FAIL;
        goto EXIT;
    }

    Insw(IDEPORT_DATA(iobase), pDeviceInfo, sizeof(IDEDeviceInfo));
    ec = NOERROR;
EXIT:
    ClearInterrupt(iobase);
    MaskControllerInterrupt(iobase, TRUE);
    return ec;
}

INLINE ECode ReadSectors(
    ioport_t iobase, AddressingMode mode,
    uint_t uDriveNo, uint16_t u16CylinderNo,
    uint8_t u8HeadNo, uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToRead, uint_t *puNumberOfSectorsRead,
    DzEvent * pEvent, int * pnIDECommand)
{
    assert(0 == uDriveNo || 1 == uDriveNo);
    assert(!(0xf0 & u8HeadNo));
    uint_t i = 0;
    uint_t uSign;
    uint8_t u8Drive = (0 == uDriveNo) ? DHR_DEV0 : (uint8_t)DHR_DEV1;
    ECode ec = WaitForBSY(iobase );
    if (FAILED(ec)) goto Exit;

    Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(mode | uDriveNo | u8HeadNo));

    ec = WaitForStatus(iobase, SR_DRDY);
    if (FAILED(ec)) goto Exit;

    Outb(IDEPORT_SECTOR_COUNT(iobase), (uint8_t)uNumberOfSectorsToRead);

    Outb(IDEPORT_SECTOR_NUMBER(iobase), u8SectorNo);

    Outb(IDEPORT_CYLINDER_LOW(iobase), LOBYTE(u16CylinderNo));

    Outb(IDEPORT_CYLINDER_HIGH(iobase), HIBYTE(u16CylinderNo));

    *pnIDECommand = IDECommand_ReadSectors;

    Outb(IDEPORT_COMMAND(iobase), IDECommand_ReadSectors);

    WaitResult wr;
    for (i = 0; i < uNumberOfSectorsToRead; i++) {
        pEvent->TryWait(1000, &wr, &uSign);
        pEvent->Clear();
        if (WaitResult_TimedOut == wr)
            kprintf("*ERROR* Read sectors timeout %d \n",
                    uDriveNo);

        ec = WaitForStatus(iobase, SR_DRQ);
        if (FAILED(ec)) {
            kprintf("*ERROR* Failed to read sectors in drive%d\n",
                    uDriveNo);
            DealWithError(iobase);
            goto Exit;
        }

        Insw(IDEPORT_DATA(iobase), pu8Buffer, SECTOR_SIZE);

        pu8Buffer += SECTOR_SIZE;
    }

Exit:
    *puNumberOfSectorsRead = i;
    *pnIDECommand = IDECommand_NOP;
    return ec;
}

INLINE ECode WriteSectors(
    ioport_t iobase, AddressingMode mode,
    uint_t uDriveNo, uint16_t u16CylinderNo,
    uint8_t u8HeadNo, uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToWrite, uint_t *puNumberOfSectorsWritten,
    DzEvent * pEvent, int * pnIDECommand)
{
    assert(0 == uDriveNo || 1 == uDriveNo);
    assert(!(0xf0 & u8HeadNo));
    uint_t i = 0;
    uint8_t u8Drive = (0 == uDriveNo) ? DHR_DEV0 : (uint8_t)DHR_DEV1;
    ECode ec = WaitForBSY(iobase );
    if (FAILED(ec)) goto Exit;

    Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(mode | u8Drive | u8HeadNo));

    ec = WaitForStatus(iobase, SR_DRDY);
    if (FAILED(ec)) goto Exit;

    Outb(IDEPORT_SECTOR_COUNT(iobase), (uint8_t)uNumberOfSectorsToWrite);

    Outb(IDEPORT_SECTOR_NUMBER(iobase), u8SectorNo);

    Outb(IDEPORT_CYLINDER_LOW(iobase), LOBYTE(u16CylinderNo));

    Outb(IDEPORT_CYLINDER_HIGH(iobase), HIBYTE(u16CylinderNo));

    *pnIDECommand = IDECommand_WriteSectors;

    Outb(IDEPORT_COMMAND(iobase), IDECommand_WriteSectors);

    WaitResult wr;
    for (i = 0; i < uNumberOfSectorsToWrite; i++) {
        ec = WaitForStatus(iobase, SR_DRQ);
        if (FAILED(ec)) {
            kprintf("*ERROR* Failed to read sectors in drive%d\n",
                    uDriveNo);
            DealWithError(iobase);
            goto Exit;
        }

        Outsw(IDEPORT_DATA(iobase), pu8Buffer, SECTOR_SIZE);
        uint_t uSign;
        pEvent->TryWait(2000, &wr, &uSign);
        pEvent->Clear();

        pu8Buffer += SECTOR_SIZE;
    }

Exit:
    *pnIDECommand = IDECommand_NOP;
    *puNumberOfSectorsWritten = i;
    return ec;
}

#endif //__ELASTOS_ATARW_H__
